package com.neural.infrastructure;

import static java.lang.Math.abs;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import com.neural.activation.Activation;
import com.neural.descriptor.NeuronDescriptor;
import com.neural.descriptor.Point;
import com.neural.randomizer.Randomizer;

public class Neuron {

	private Double value;
	private Point position;
	private Double conscience;
	private List<Connection> preConnections;

	public Neuron() {
		this.value = 0.0;
		this.conscience = 1.0;
		this.preConnections = new LinkedList<Connection>();
	}

	public Neuron(NeuronDescriptor neuronDescriptor, ILayer previousLayer, Randomizer randomizer, Point position) {
		this();
		if (previousLayer != null) {
			this.createConnections(neuronDescriptor, previousLayer, randomizer, position);
		}
	}

	private void createConnections(NeuronDescriptor neuronDescriptor, ILayer previousLayer, Randomizer randomizer, Point position) {
		for (int i = 0; i < previousLayer.getNeurons().size(); i++) {

			if (neuronDescriptor.getWeights() == null) {
				neuronDescriptor.setWeights(createWeights(previousLayer.getNeurons().size(), randomizer));
			}

			Neuron preNeuron = previousLayer.getNeurons().get(i);
			Double weight = neuronDescriptor.getWeights().get(i);
			Connection connection = new Connection(preNeuron, (weight != null) ? weight : randomizer.generateDouble());
			this.addPreConnection(connection);
		}
		this.position = position;
	}

	private List<Double> createWeights(int size, Randomizer randomizer) {
		List<Double> weights = new ArrayList<Double>(size);
		for (int i = 0; i < size; i++) {
			weights.add(randomizer.generateDouble());
		}
		return weights;
	}

	public Double getConscience() {
		return conscience;
	}

	public void addConscience(Double conscience) {
		this.conscience += conscience;
	}

	public void subConscience(Double conscience) {
		this.conscience -= conscience;
	}

	public Double getValue() {
		return value;
	}

	public void setValue(Double value) {
		this.value = value;
	}

	public void addPreConnection(Connection connection) {
		preConnections.add(connection);
	}

	public List<Connection> getPreConnections() {
		return preConnections;
	}

	public void compute(Activation activation, Double learningSpeed, Double neighbourhood) {

		if (preConnections.size() > 0) {
			for (Connection connection : preConnections) {
				double delta = connection.getPreNeuron().getValue() - connection.getWeight();
				double connectionWeight = learningSpeed * neighbourhood * delta;
				connection.addWeight(connectionWeight);
			}
		}
	}

	public List<Double> calculateDistances() {

		List<Double> distances = new ArrayList<Double>();
		for (Connection connection : preConnections) {

			distances.add(abs(connection.getWeight() - connection.getPreNeuron().getValue()));
		}
		return distances;
	}

	public Point getPosition() {
		return position;
	}

}
